import re
from time import time
from typing import Optional
from urllib.parse import unquote
from uuid import uuid4

from fastapi import Request, status, BackgroundTasks
from loguru import logger
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
from starlette.types import ASGIApp
from tenacity import retry, stop_after_attempt, wait_exponential
from user_agents import parse

from app.core.security import decode_token
from app.crud.login_log_crud import LoginLogCRUD
from app.db.session_factory import get_async_context_manager
from app.deps.crud_deps import get_global_crud_factory
from app.models import LoginLog
from app.schemas.base_schema import LoginAccountType, DeviceType
from app.schemas.login_log_schema import LoginLogCreate


class LoginLogMiddleware(BaseHTTPMiddleware):
    def __init__(self, app: ASGIApp) -> None:
        super().__init__(app)

    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        # 只记录登录和刷新Token日志
        included_paths = ["/login","/refresh_token"]
        if not any(path in request.url.path for path in included_paths):
            return await call_next(request)

        start_time = time()
        response = None
        message = None
        is_success = True
        status_code = status.HTTP_500_INTERNAL_SERVER_ERROR  # 默认值，会在异常时更新


        """
        fastAPI的BackgroundTasks默认情况下：
            不会创建新线程，而是在当前事件循环中调度任务
            任务会在请求响应完成后立即执行
            使用与主请求相同的线程
        """
        # 获取BackgroundTasks
        background_tasks = BackgroundTasks()

        try:
            # 存储请求体（限制大小）
            body = await request.body()
            request.state.body = body[:1024 * 1024] if len(body) > 1024 * 1024 else body

            request.state.background_tasks = background_tasks

            # 执行请求处理
            response = await call_next(request)
            status_code = response.status_code
            is_success = status_code < 400
        except Exception as e:
            # 不处理异常，但提取异常信息供日志记录
            message = str(e)
            is_success = False
            # 从异常对象提取 status_code（如果是 HTTPException）
            status_code = getattr(e, "status_code", 500)
            raise  # 仍抛出异常，由全局异常中间件处理
        finally:
            # 确保记录日志（无论是否发生异常）
            elapsed_time = int((time() - start_time) * 1000)
            log = await self._log_operation(request, elapsed_time, status_code, is_success, message)

            # 添加后台任务
            background_tasks.add_task(self.add_log, log)
            # 如果response存在，添加后台任务
            if response:
                response.background = background_tasks
        return response

    """
    stop=stop_after_attempt(3)
        设置重试停止条件
        当函数执行失败后，最多重试 3次（包括最初的第一次尝试）
        总共最多会执行 1（初始） + 3（重试） = 4 次
    wait=wait_exponential(multiplier=1, min=4, max=10)
        设置每次重试之间的等待时间策略
        multiplier=1：等待时间 = 2^attempt * multiplier（秒）
        min=4：最小等待4秒
        max=10：最大等待10秒
    实际重试间隔：
        第一次重试：max(2^1 * 1, 4) = max(2, 4) = 4秒
        第二次重试：max(2^2 * 1, 4) = max(4, 4) = 4秒
        第三次重试：max(2^3 * 1, 4) = max(8, 4) = 8秒
        （第四次尝试将受限于max=10，最多等待10秒）
    """
    @staticmethod
    @retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
    async def add_log(log: dict) :
        async with get_async_context_manager() as session:  # 在 with 块内完成所有操作
            try:
                crud = get_global_crud_factory(LoginLogCRUD, LoginLog, session)
                await crud.create(obj=LoginLogCreate(**log))
            except Exception as e:
                logger.exception(f"登录日志记录失败: {str(e)}", exc_info=True)


    @staticmethod
    async def _log_operation(
        request: Request,
        elapsed_time: int,
        status_code: int,
        is_success: bool,
        message: Optional[str] = None,
    ) -> dict:
        user_id = uuid4()
        account = ""
        account_type = LoginAccountType.username
        device_id =  uuid4()
        device_type = DeviceType.pc

        token_data = getattr(request.state, "token_data", None)
        if token_data:
            user_id =token_data.sub
            account = token_data.account
            account_type = token_data.account_type
            device_id = token_data.device_id
            device_type = token_data.device_type
        elif "/login" in request.url.path:
            # 获取请求参数
            params = str(request.state.body)
            encoded_params = unquote(params)
            matches = re.findall(r"account=(.*)&", str(encoded_params))
            if matches is None or len(matches) == 0:
                matches = re.findall(r"username=(.*)&", str(encoded_params))
            if matches is not None and len(matches) > 0:
                account = matches[0]
        elif "/refresh_token" in request.url.path:
            authorization = str(request.headers.get("Authorization")).split(" ")
            token = authorization[1] if len(authorization) == 2 else None
            if token is not None:
                payload = decode_token(token)
                user_id = payload.get("sub")
                account = payload.get("account")
                account_type = payload.get("account_type")
                device_id = payload.get("device_id")
                device_type = payload.get("device_type")

        user_agent = request.headers.get("User-Agent")
        ua = parse(user_agent)
        browser = ua.browser.family if ua.browser else None
        os = ua.os.family if ua.os else None
        ip_address = request.client.host if request.client else None

        # 获取响应结果
        log = {
            "user_id": user_id,
            "account": account,
            "account_type": account_type,
            "api_path" : request.url.path,
            "ip_address": ip_address,
            "browser": browser,
            "os": os,
            "device_id": device_id,
            "device_type": device_type,
            "user_agent": user_agent,
            "elapsed_time": elapsed_time,
            "status_code": status_code,
            "is_success": is_success,
            "message": message,
        }

        return log
